#pragma once

#include <QtCore/QObject>
#include <QtCore/QUuid>
#include <QtCore/QVariant>

#include "QtNodeData.hpp"

#include "QtSerializable.hpp"
#include "QtDataModelRegistry.hpp"
#include "Export.hpp"

class QPointF;

namespace Qt
{
	class QtNode;
	class QtNodeData;
	class QtConnectionGraphicsObject;

	class QtNode;

	class ConnectionGeometry
	{
	public:

		ConnectionGeometry();

	public:

		QPointF const&
			getEndPoint(PortType portType) const;

		void
			setEndPoint(PortType portType, QPointF const& point);

		void
			moveEndPoint(PortType portType, QPointF const &offset);

		QRectF
			boundingRect() const;

		std::pair<QPointF, QPointF>
			pointsC1C2() const;

		QPointF
			source() const { return _out; }
		QPointF
			sink() const { return _in; }

		double
			lineWidth() const { return _lineWidth; }

		bool
			hovered() const { return _hovered; }
		void
			setHovered(bool hovered) { _hovered = hovered; }

	private:
		// local object coordinates
		QPointF _in;
		QPointF _out;

		//int _animationPhase;

		double _lineWidth;

		bool _hovered;
	};

	/// Stores currently draggind end.
	/// Remembers last hovered QtNode.
	class ConnectionState
	{
	public:

		ConnectionState(PortType port = PortType::None)
			: _requiredPort(port)
		{}

		ConnectionState(const ConnectionState&) = delete;
		ConnectionState operator=(const ConnectionState&) = delete;

		~ConnectionState();

	public:

		void setRequiredPort(PortType end)
		{
			_requiredPort = end;
		}

		PortType requiredPort() const
		{
			return _requiredPort;
		}

		bool requiresPort() const
		{
			return _requiredPort != PortType::None;
		}

		void setNoRequiredPort()
		{
			_requiredPort = PortType::None;
		}

	public:

		void interactWithNode(QtNode* node);

		void setLastHoveredNode(QtNode* node);

		QtNode*
			lastHoveredNode() const
		{
			return _lastHoveredNode;
		}

		void resetLastHoveredNode();

	private:

		PortType _requiredPort;

		QtNode* _lastHoveredNode{ nullptr };
	};

	///
	class NODE_EDITOR_PUBLIC QtConnection
		: public QObject
		, public QtSerializable
	{

		Q_OBJECT

	public:

		/// New QtConnection is attached to the port of the given QtNode.
		/// The port has parameters (portType, portIndex).
		/// The opposite connection end will require anothre port.
		QtConnection(PortType portType,
			QtNode& node,
			PortIndex portIndex);

		QtConnection(QtNode& nodeIn,
			PortIndex portIndexIn,
			QtNode& nodeOut,
			PortIndex portIndexOut,
			TypeConverter converter =
			TypeConverter{});

		QtConnection(const QtConnection&) = delete;
		QtConnection operator=(const QtConnection&) = delete;

		~QtConnection();

	public:

		QJsonObject
			save() const override;

	public:

		QUuid
			id() const;

		/// Remembers the end being dragged.
		/// Invalidates QtNode address.
		/// Grabs mouse.
		void
			setRequiredPort(PortType portType);
		PortType
			requiredPort() const;

		void
			setGraphicsObject(std::unique_ptr<QtConnectionGraphicsObject>&& graphics);

		/// Assigns a node to the required port.
		/// It is assumed that there is a required port, no extra checks
		void
			setNodeToPort(QtNode& node,
				PortType portType,
				PortIndex portIndex);

		void
			removeFromNodes() const;

	public:

		QtConnectionGraphicsObject&
			getConnectionGraphicsObject() const;

		ConnectionState const &
			connectionState() const;
		ConnectionState&
			connectionState();

		ConnectionGeometry&
			connectionGeometry();

		ConnectionGeometry const&
			connectionGeometry() const;

		QtNode*
			getNode(PortType portType) const;

		QtNode*&
			getNode(PortType portType);

		PortIndex
			getPortIndex(PortType portType) const;

		void
			clearNode(PortType portType);

		NodeDataType
			dataType(PortType portType) const;

		void
			setTypeConverter(TypeConverter converter);

		bool
			complete() const;

	public: // data propagation

		void propagateData(std::shared_ptr<QtNodeData> nodeData) const;

		void propagateEmptyData() const;

		void propagateDisconnectedData() const;

	Q_SIGNALS:

		void
			connectionCompleted(QtConnection const&) const;

		void
			connectionMadeIncomplete(QtConnection const&) const;

	private:

		QUuid _uid;

	private:

		QtNode* _outNode = nullptr;
		QtNode* _inNode = nullptr;

		PortIndex _outPortIndex;
		PortIndex _inPortIndex;

	private:

		ConnectionState    _connectionState;
		ConnectionGeometry _connectionGeometry;

		std::unique_ptr<QtConnectionGraphicsObject>_connectionGraphicsObject;

		TypeConverter _converter;

	Q_SIGNALS:

		void
			updated(QtConnection& conn) const;
	};
}
